package com.kyb.cms.util.oss;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.armada.galileo.common.util.CommonUtil;
import org.armada.galileo.exception.BizException;
import org.armada.galileo.mvc_plus.domain.UploadResult;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.*;

@Slf4j
public class AliYunOssUtil   {

    /**
     * 内网地址
     */
    private String endpoint;

    private String accessKeyId;

    private String accessKeySecret;

    private String bucketName;

    private OSS ossClient;

    private OSS ossClientPublic;

    private ResourceApiConfig apiConfig;

    public static AliYunOssUtil instance(ResourceApiConfig apiConfig) {

        AliYunOssUtil aliYunOssUtil = new AliYunOssUtil();
        aliYunOssUtil.endpoint = apiConfig.getEndpoint();
        aliYunOssUtil.accessKeyId = apiConfig.getAccessKeyId();
        aliYunOssUtil.accessKeySecret = apiConfig.getAccessSecret();
        aliYunOssUtil.bucketName = apiConfig.getBucketName();
        aliYunOssUtil.apiConfig = apiConfig;

        aliYunOssUtil.init();
        return aliYunOssUtil;
    }

    private void init() {
        // 创建ClientConfiguration实例，您可以根据实际情况修改默认参数。
        ClientBuilderConfiguration conf = new ClientBuilderConfiguration();
        // 设置是否支持CNAME。CNAME用于将自定义域名绑定到目标Bucket。
        conf.setSupportCname(true);
        // 创建OSSClient实例。
        log.info("初始化 oss 实例, endPoint:{}, accessKeyId:{}, accessKeySecret:{}", endpoint, accessKeyId, accessKeySecret);
        this.ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret, conf);
        this.ossClientPublic = new OSSClientBuilder().build(this.apiConfig.getPublicEndpoint(), accessKeyId, accessKeySecret, conf);
    }

    /**
     * 生成一临时链接
     *
     * @param objectName
     * @return
     */
    public String generateTmpUrl(String objectName) {
        String url = generateTmpUrl(objectName, 15 * 24 * 3600 * 1000);
        log.info("[aliyun-oss] generateTmpUrl, bucket:{}, objectName:{}, url: {}", bucketName, objectName, url);
        return url;
    }


    public String generatePublicUrl(String objectName) {
        String url = apiConfig.getPublicEndpoint() + "/" + objectName;

        log.info("[aliyun-oss] generatePublicUrl, bucket:{}, objectName:{}, url: {}", bucketName, objectName, url);

        return url;
    }

    /**
     * 生成url访问链接
     *
     * @param objectName   对象 key
     * @param durationTime 过期时间
     * @return
     */
    public String generateTmpUrl(String objectName, long durationTime) {

        // 设置URL过期时间为1小时。
        Date expiration = new Date(System.currentTimeMillis() + durationTime);

        // 生成以GET方法访问的签名URL，访客可以直接通过浏览器访问相关内容。
        URL url = ossClientPublic.generatePresignedUrl(bucketName, objectName, expiration);

        return url.toString();
    }

    /**
     * 删除对象
     *
     * @param objectName
     */
    public void delete(String objectName) {

        log.info("[aliyun-oss] delete, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);

        ossClient.deleteObject(bucketName, objectName);
    }

    /**
     * 上传对象
     *
     * @param objectName 对象名
     * @param data       对象内容
     */
    public void upload(String objectName, byte[] data) {

        if (objectName == null || objectName.indexOf("/") == -1) {
            throw new BizException("禁止上传到根路径");
        }

        log.info("[aliyun-oss] upload, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);

        try {
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(data));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BizException(e);
        }

    }

    /*public void appendUpload(){

        // 第一次追加。
        // 设置文件的追加位置。
        appendObjectRequest.setPosition(0L);
        AppendObjectResult appendObjectResult = ossClient.appendObject(appendObjectRequest);
        // 文件的64位CRC值。此值根据ECMA-182标准计算得出。
        System.out.println(appendObjectResult.getObjectCRC());

        // 第二次追加。
        // nextPosition表示下一次请求中应当提供的Position，即文件当前的长度。
        appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
        appendObjectRequest.setInputStream(new ByteArrayInputStream(content2.getBytes()));
        appendObjectResult = ossClient.appendObject(appendObjectRequest);

        // 第三次追加。
        appendObjectRequest.setPosition(appendObjectResult.getNextPosition());
        appendObjectRequest.setInputStream(new ByteArrayInputStream(content3.getBytes()));
        appendObjectResult = ossClient.appendObject(appendObjectRequest);
    }*/



    /**
     * 上传对象至阿里云
     *
     * @param objectName
     * @param is
     */
    public void upload(String objectName, InputStream is) {
        if (objectName == null || objectName.indexOf("/") == -1) {
            throw new BizException("禁止上传到根路径");
        }
        log.info("[aliyun-oss] upload, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);
        try {
            ossClient.putObject(bucketName, objectName, is);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BizException(e);
        }
    }


    /**
     * 根据文件名获取oss对象
     *
     * @param objectName
     * @return
     */
    public InputStream downloadInputstream(String objectName) {

        log.info("[aliyun-oss] downloadInputstream, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);

        try {
            OSSObject object = ossClient.getObject(bucketName, objectName);

            return object.getObjectContent();

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BizException(e);
        }
    }

    /**
     * 下载文件
     *
     * @param objectName
     * @return
     * @throws Exception
     */
    public byte[] download(String objectName) {

        byte[] result = null;

        log.info("[aliyun-oss] download, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);

        try {

            // 调用ossClient.getObject返回一个OSSObject实例，该实例包含文件内容及文件元信息。
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);

            // 调用ossObject.getObjectContent获取文件输入流，可读取此输入流获取其内容。
            InputStream content = ossObject.getObjectContent();

            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            if (content != null) {

                byte[] buf = new byte[10000];
                int len = 0;
                while ((len = content.read(buf)) != -1) {
                    bos.write(buf, 0, len);
                }
                // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
                content.close();
            }
            bos.close();

            result = bos.toByteArray();

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BizException(e);
        }

        return result;
    }


    /**
     * 下载到流
     *
     * @param os
     * @param objectName
     */
    public void download2Outputstream(OutputStream os, String objectName) {

        log.info("[aliyun-oss] download2Outputstream, bucket:{}, objectName:{}, endpoint: {}", bucketName, objectName, endpoint);

        try {

            // 调用ossClient.getObject返回一个OSSObject实例，该实例包含文件内容及文件元信息。
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            // 调用ossObject.getObjectContent获取文件输入流，可读取此输入流获取其内容。
            InputStream content = ossObject.getObjectContent();

            if (content != null) {

                byte[] buf = new byte[10000];
                int len = 0;
                while ((len = content.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }

                // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
                content.close();
            }
        } catch (Exception e) {

            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }


    public List<RemoteResourceFile> ls(String folder) {

        log.info("[aliyun-oss] ls, bucket:{}, objectName:{}, endpoint: {}", bucketName, folder, endpoint);


        List<RemoteResourceFile> result = new ArrayList<RemoteResourceFile>();
        try {

            ListObjectsRequest listObjectsRequest = new ListObjectsRequest();

            listObjectsRequest.setBucketName(bucketName);
            listObjectsRequest.setPrefix(folder);

            ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);

            List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
            for (OSSObjectSummary s : sums) {

                RemoteResourceFile obj = new RemoteResourceFile();
                obj.setKey(s.getKey());
                obj.setLastModified(s.getLastModified());
                obj.setSize(s.getSize());
                obj.seteTag(s.getETag());

                result.add(obj);
            }

        } catch (Exception e) {

            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }

        return result;

    }




    private static String allFilesExts = "apk|jpg|jpeg|png|gif|bmp|msg|pdf|doc|docx|rar|zip|xls|xlsx|txt|mp3|wav|cda|ttf|tif|mp4|flv|mpeg|rmvb|avi|mov|tif|jar|db|sql|json|zip|yml|xml|properties";

    private static List<String> allowTypes = Arrays.asList(allFilesExts.split("\\|"));


    /**
     * 上传request中的流对象
     *
     * @param folder       上传根目录
     * @param keepFileName ossname 中是否保留原文件 名
     * @param request
     * @return
     * @throws Exception
     */
    public UploadResult uploadByServletRequest(String folder, boolean keepFileName, HttpServletRequest request) throws Exception {

        String encoding = "utf-8";
        request.setCharacterEncoding(encoding);

        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload sevletFileUpload = new ServletFileUpload(factory);
        sevletFileUpload.setSizeMax(500 * 1024 * 1024); // 500M
        List<FileItem> fileItems = sevletFileUpload.parseRequest(request);

        // 上传后的路径
        String uploadObjectName = null;

        // 依次处理每个上传的文件
        for (Iterator<FileItem> it = fileItems.iterator(); it.hasNext(); ) {

            final FileItem item = (FileItem) it.next();
            if (!item.isFormField()) {
                String fileFullName = item.getName();
                int tmpIndex = fileFullName.lastIndexOf(".");
                if (tmpIndex == -1) {
                    throw new BizException("上传类型不支持");
                }

                String ext = fileFullName.substring(tmpIndex + 1).toLowerCase();
                if (!allowTypes.contains(ext)) {
                    throw new BizException("上传类型不支持:" + ext);
                }

                InputStream is = item.getInputStream();
                long fileSize = is.available();

                if (!keepFileName) {
                    uploadObjectName = folder + "/" + CommonUtil.format(new Date(), "yyyyMMdd") + "/" + UUID.randomUUID().toString() + "." + ext;
                } else {
                    uploadObjectName = folder + "/" + CommonUtil.format(new Date(), "yyyyMMdd") + "/" + fileFullName.toLowerCase();
                }

                upload(uploadObjectName, is);

                is.close();

                UploadResult result = new UploadResult();
                result.setFileExt(ext);
                result.setFileName(fileFullName);
                result.setFilePath(uploadObjectName);
                result.setFileSize(fileSize);
                result.setUploadTime(new Date());

                return result;
            }
        }
        return null;
    }


}
